<?php
/**
 * Description:
 * Author: hezhizheng
 * Date: 2018/11/13
 * Time: 16:54
 * Created by Created by Panxsoft.
 */

namespace App\Models\Products;

use App\Models\Concerns\ModelMemorable;
use App\Models\Concerns\ScopeFilterable;
use App\Models\Currency;
use App\Models\Customers\CustomerGroup;
use App\Models\IndexConfig\ProductIndexConfigurator;
use App\Models\Order\OrderProduct;
use App\Models\Order\OrderStatus;
use App\Services\Customers\AppCustomer;
use App\Services\Locale\Language;
use App\Models\StockStatus;
use App\Models\Traits\ProductFilter;
use App\Services\MobileApp\AppEnv;
use App\Services\Utils\ImageHelper;
use App\Services\Utils\Memorable;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use ScoutElastic\Searchable;

/**
 * Class Product
 *
 * @property int product_id
 * @property string price - 原价
 * @property string viewed - 商品查看次数
 * @property string quantity - 库存
 * @property string minimum - 起订数量
 * @property string origin_price - 原价
 * @property string date_available - 开售日期 (date)
 * @property array $discounts_price - 折扣价 (区间)
 *
 * @property bool|null is_wish_product - 当前用户是否收藏该商品
 * @property string currency_symbol_left - Currency - 汇率左符号
 * @property string currency_symbol_right - Currency - 汇率右符号
 *
 * @property-read string $image
 * @property-read string $origin_image
 * @property string $settle_price - 一件产品结算价
 *
 * @property Collection filterReviewsByStatusOn
 *
 * @property ProductDiscount filterProductDiscountByLatest - 产品折扣（一件价格）
 * @property ProductSpecial filterProductSpecialByLatest - 产品特价
 * @property-write string|null special_price - ProductSpecial 的特价（产品特价）
 *
 * @property Collection productDiscounts
 * @property Collection|ProductAttribute[] productAttributes
 * @property Collection|ProductAttribute[] product_attribute_values - 经过处理的 productAttributes?
 * @property Collection filterReviewsByLimit
 *
 * @property ProductDescription productDescription - 产品描述
 * @property string name - productDescription - 标题
 * @property string meta_title - productDescription - 网页 meta 标签标题
 * @property StockStatus|null stockStatus
 *
 * @property-write string special_format 特价（带货币符号）
 * @property-write string price_format 原价（带货币符号）
 * @property Collection discounts
 * @property Collection bestSellerProducts
 * @property-write int selling_quantity - 总销量
 * @property ProductDescription filterProductDescriptionByLanguage
 * @property Collection productSpecials
 * @property string sales_volume
 * @property Collection orderProducts
 * @property Collection productOptions
 * @property-read ProductSpecial|null productSpecial - 产品对应：限定时间内、当前用户组、优先级最高 的特价
 * @property-read ProductDiscount|null productDiscount - 产品对应：限定时间内、当前用户组、优先级最高、一件起批 的折扣价
 * @property-write string currency_settle_price - 根据当前汇率转换的产品结算价格
 * @property string symbol_left
 * @property string symbol_right
 *
 * @package App\Models\Products
 */
class Product extends Model
{
    use ScopeFilterable;
    use ProductFilter;
    use ModelMemorable;

    /**
     * $indexConfigurator / $searchRules / $mapping 为使用 ScoutElastic 必须的参数
     */
    use Searchable;
    protected $indexConfigurator = ProductIndexConfigurator::class;
    protected $searchRules = [];
    protected $mapping = [
        'properties' => [
            'model' => [
                'type' => 'text',
                'fields' => [
                    'raw' => [
                        'type' => 'keyword',
                    ]
                ]
            ],
            'sku' => [
                'type' => 'text',
                'fields' => [
                    'raw' => [
                        'type' => 'keyword',
                    ]
                ]
            ],
        ]
    ];

    const PRODUCT_QUANTITY_DEFAULT = 0; // 默认商品的库存数量
    const PRODUCT_PRICE_DEFAULT = 0; // 默认价格
    const DEFAULT_LIMIT_COUNT = 1; // 默认查询条数
    const PRODUCT_STATUS_ON = 1; // 商品出售中
    const PRODUCT_STATUS_OFF = 0; // 商品下架中

    const PRODUCT_MINIMUM_QUANTITY = 1; // 商品最小起订数

    const ACTUAL_PRICE_SQL = "coalesce(
    (SELECT price FROM oc_product_special ps 
    WHERE ps.product_id = oc_product.product_id 
    AND ps.customer_group_id = '%d' 
    AND ((ps.date_start = '0000-00-00' OR ps.date_start < NOW()) AND (ps.date_end = '0000-00-00' OR ps.date_end > NOW())) 
    ORDER BY ps.priority ASC, ps.price ASC 
    LIMIT ".ProductSpecial::PRODUCT_SPECIAL_NUM."), 
    (SELECT price FROM oc_product_discount pd2 
    WHERE pd2.product_id = oc_product.product_id 
    AND pd2.customer_group_id = '%d' 
    AND pd2.quantity = '".ProductDiscount::QUANTITY_DEFAULT."' 
    AND ((pd2.date_start = '0000-00-00' OR pd2.date_start < NOW()) AND (pd2.date_end = '0000-00-00' OR pd2.date_end > NOW())) 
    ORDER BY pd2.priority ASC, pd2.price ASC 
    LIMIT ".ProductDiscount::PRODUCT_DISCOUNT_NUM."), 
    oc_product.price) 
    AS actual_price";

    const SALES_VOLUME_SQL = "(SELECT SUM(od.quantity) FROM oc_order_product od 
    INNER JOIN oc_order o ON o.order_id = od.order_id 
    WHERE od.product_id = oc_product.product_id 
    AND o.order_status_id > '".OrderStatus::ORDER_STATUS_ID_DEFAULT."' 
    GROUP BY od.product_id ) 
    AS sales_volume";

    const REVIEW_SQL = "(SELECT COUNT(ore.review_id) FROM oc_review ore 
    WHERE ore.product_id = oc_product.product_id 
    GROUP BY ore.product_id) 
    AS review";

    protected $guarded = [];

    protected $table = 'product';

    protected $primaryKey = 'product_id';
    protected $appends = ['origin_image'];

    public $timestamps = false;

    /**
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function productImages()
    {
        return $this->hasMany(ProductImage::class, 'product_id', 'product_id')
            ->orderBy('sort_order');
    }

    public function stockStatus()
    {
        return $this->belongsTo(StockStatus::class,'stock_status_id')
            ->where('language_id',AppEnv::languageId());
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\hasMany
     */
    public function productAttributes()
    {
        return $this->hasMany(ProductAttribute::class,'product_id','product_id')
            ->where('language_id',AppEnv::languageId());
    }

    public function productDescription()
    {
        return $this->hasOne(ProductDescription::class,'product_id','product_id')
            ->where('language_id',AppEnv::languageId());
    }

    /**
     * @used-by $productSpecial
     *
     * @return HasOne
     */
    public function productSpecial()
    {
        /** @var HasOne|ProductSpecial $relate */
        $relate = $this
            ->hasOne(ProductSpecial::class,'product_id','product_id')
            ->where('customer_group_id', AppEnv::customerGroupId())
            ->oldest('priority');

        return $relate->filterLimitSpecifiedRangeDate();
    }

    /**
     * @return HasMany
     */
    public function productOptions()
    {
        return $this->hasMany(ProductOption::class, 'product_id', 'product_id')
            ->with(['option.optionDescription','productOptionValues.optionValueDescription']);
    }

    /**
     * @return HasMany
     */
    public function productOptionValues()
    {
        return $this->hasMany(ProductOptionValue::class, 'product_id', 'product_id');
    }

    /**
     * @return HasMany
     */
    public function productOptionValuesByType()
    {
        return $this->hasMany(ProductOptionValue::class, 'product_id', 'product_id')
            ->with('optionType','productOption');
    }

    public function orderProducts()
    {
        return $this->hasMany(OrderProduct::class, 'product_id', 'product_id');
    }

    public function productToCategories()
    {
        return $this->hasMany(ProductToCategory::class, 'product_id', 'product_id');
    }

    /**
     * @used-by $origin_image
     * @return null|string
     */
    public function getOriginImageAttribute()
    {
        $route = $this->attributes['image'] ?? null;

        if (!$route) return null;

        return ImageHelper::generateImagePath($route) ;
    }

    /**
     * @used-by $image
     * @return null|string
     * @throws \Exception
     */
    public function getImageAttribute()
    {
        if (! isset($this->attributes['image'])) return null;

        return ImageHelper::generateImagePath($this->attributes['image']);
    }

    /**
     * @used-by $product_video
     * @return null|string
     * @throws \Exception
     */
    public function getProductVideoAttribute()
    {
        if (empty($this->attributes['product_video'])) return null;

        return ImageHelper::generateImagePath($this->attributes['product_video']);
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function productToStore()
    {
        return $this->belongsTo(ProductToStore::class, 'product_id', 'product_id');
    }

    /**
     * @deprecated (改用productDescription)
     * @return \Illuminate\Database\Eloquent\Relations\HasOne
     */
    public function filterProductDescriptionByLanguage()
    {
        return $this->hasOne(ProductDescription::class, 'product_id', 'product_id')
            ->where('language_id', AppEnv::languageId());
    }

    /**
     * @deprecated (改用productDiscounts)
     *
     * @used-by $filterProductDiscountByLatest
     *
     * @return \Illuminate\Database\Eloquent\Relations\HasOne
     */
    public function filterProductDiscountByLatest()
    {
        $now = now();

        return $this->hasOne(ProductDiscount::class, 'product_id', 'product_id')
            ->where('customer_group_id', AppCustomer::groupId())
            ->where('quantity', ProductDiscount::QUANTITY_DEFAULT) // 起购数量？
            ->where('date_start', '<', $now)
            ->where('date_end', '>', $now)
            ->orderBy('priority','ASC')
            ->orderBy('price','ASC')
            ->limit(self::DEFAULT_LIMIT_COUNT);
    }

    /**
     * @deprecated (用下面的了)
     *
     * @used-by $filterProductSpecialByLatest
     *
     * @return \Illuminate\Database\Eloquent\Relations\HasOne
     */
    public function filterProductSpecialByLatest()
    {
        /** @var \Illuminate\Database\Eloquent\Relations\HasOne|ProductSpecial $special */
        $special = $this->hasOne(ProductSpecial::class, 'product_id', 'product_id');

        return $special
            ->filterLimitSpecifiedRangeDate()
            ->where('customer_group_id', AppCustomer::groupId())
            ->orderBy('priority','ASC')
            ->orderBy('price','ASC');
    }

    /**
     * @used-by $productSpecials
     *
     * @return ProductSpecial|HasMany
     */
    public function productSpecials()
    {
        /** @var HasMany|ProductSpecial $related */
        $related = $this->hasMany(ProductSpecial::class, 'product_id', 'product_id')
            ->where('customer_group_id', AppCustomer::groupId());

        return $related
            ->filterLimitSpecifiedRangeDate()
            ->oldest('priority');
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function reviews()
    {
        return $this->hasMany(Review::class, 'product_id', 'product_id');
    }

    /**
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function filterReviewsByStatusOn()
    {
        return $this->hasMany(Review::class, 'product_id', 'product_id')
            ->where('status', Review::REVIEW_STATUS_ON);
    }

    public function productDiscounts()
    {
        /** @var HasMany|ProductDiscount $related */
        $related = $this->hasMany(ProductDiscount::class, 'product_id', 'product_id')
            ->where('customer_group_id', AppCustomer::groupId());

        return $related
            ->filterLimitSpecifiedRangeDate()
            ->orderBy('quantity','ASC')
            ->orderBy('priority','ASC');

    }

    /**
     * @used-by $productDiscount
     *
     * @return ProductDiscount|HasOne
     */
    public function productDiscount()
    {
        /** @var HasOne|ProductDiscount $related */
        $related = $this->hasOne(ProductDiscount::class, 'product_id', 'product_id')
            ->where('customer_group_id', AppCustomer::groupId());

        return $related
            ->filterLimitSpecifiedRangeDate()
            ->orderBy('priority','ASC')
            ->where('quantity',ProductDiscount::QUANTITY_DEFAULT);
    }

    public static function getProduct($product_id)
    {
        return (new self)->where('product_id', $product_id)
            ->first();
    }

    /**
     * @param Product $product
     * @param $quantity
     * @return mixed|null|string
     */
    public static function getProductActualPrice(Product $product, $quantity)
    {
        // 特价优先级高于折扣价
        // TODO 在循环中会重复查询应提交 with 关联关系
        $product_special = ProductSpecial::getProductSpecial($product);
        if ($product_special) {
            return $product_special->price;
        }

        $product_discount = ProductDiscount::getProductDiscount($product, $quantity);
        if ($product_discount) {
            $product_discount->price;
        }

        return $product->price;
    }

    /**
     * @return Builder|HasMany
     */
    public function bestSellerProducts()
    {
        return $this->hasMany(OrderProduct::class, 'product_id', 'product_id')
            ->whereHas('order', function(Builder $query){
                return $query->where('order_status_id', '>', OrderStatus::ORDER_STATUS_ID_DEFAULT);
            });
    }

    /**
     * @param Product $product
     * @return mixed
     */
    public static function getProductSalesVolume(Product $product)
    {
        $order_products = $product->orderProducts;

        /** @var \Illuminate\Support\Collection $order_products */
        $order_products = $order_products->filter(function(OrderProduct $order_product){
            return $order_product->orderByPaid;
        });

        return $order_products->sum('quantity');
    }


    /**
     * (app购物车和商品详情在用)
     * @param Product $product
     * @return string
     */
    public static function getProductOriginPrice(Product $product)
    {
        /** @var Currency $currency */
        $currency = AppEnv::currency();

        $product_price = $product->price;
        $default_product_discount_price = null;

        // 起购数量是1的折扣价格。
        $product_discounts = $product->productDiscounts->unique('quantity');
        $product_discount_default = $product_discounts->filter(function(ProductDiscount $product_discount) use(&$default_product_discount_price){

            $quantity = $product_discount->quantity;
            if ($quantity === ProductDiscount::QUANTITY_DEFAULT) {
                $default_product_discount_price = $product_discount->price;
            }

            return $quantity !== ProductDiscount::QUANTITY_DEFAULT;
        })
            ->values();

        $product->productDiscounts = $product_discount_default;

        $price = $default_product_discount_price ?? $product_price;

        $origin_price = $currency->exchangePrice($price);

        return $origin_price;
    }

    /**
     * @used-by $settle_price
     * 商品的当前价格，优先取special_price，之后去起购数量为1的折扣价，最后是product_price。
     *
     * @return string
     */
    public function getSettlePriceAttribute()
    {
        return $this->calcSettlePrice(ProductDiscount::QUANTITY_DEFAULT, []);
    }

    /**
     * 统计商品选项价格
     * @param array $option
     * @return int|float
     */
    public function getCartProductOptionPrice(array $option)
    {
        $option_price = 0.00;
        if (! count($option)) return $option_price;

        $product_option_values = (new ProductOptionValue)
            ->whereIn('product_option_id', array_keys($option))
            ->where('product_id', $this->product_id)
            ->where(function(Builder $query) use($option){

                foreach($option as $product_option_id => $product_option_value_id)
                {
                    $query->orWhere(function(Builder $query) use($product_option_id, $product_option_value_id){
                        $product_option_ids = is_array($product_option_value_id)
                            ? $product_option_value_id
                            : [$product_option_value_id];

                         $query->where('product_option_id', $product_option_id)
                             ->whereIn('product_option_value_id', $product_option_ids);
                    });
                }
            })
            ->get();

        // 统计所有选项的价格
        $product_option_values->transform(function(ProductOptionValue $product_option_value) use(&$option_price){

            if ($product_option_value->price_prefix === '+') {
                $option_price += $product_option_value->price;
            } else {
                $option_price -= $product_option_value->price;
            }
        });

        return $option_price;
    }


    /**
     * 指定数量的商品结算单价
     *
     * @param int $quantity - 订购的商品数量
     *
     * @param array $option
     * @return string|float
     */
    public function calcSettlePrice(int $quantity, array $option): string
    {
        // 根据商品选项值，获取价格偏移值。
        $option_price = $this->getCartProductOptionPrice($option);

        // 优先使用 productSpecial 特价
        /** @var ProductSpecial|null $special */
        if ($special = $this->productSpecials->first()) {
            $price = (float)($special->price) + $option_price;
            return $price;
        }

        $price = $option_price + $this->calcDiscountPrice($quantity);

        return $price;
    }

    /**
     * 如果折扣则使用 productDiscounts 对应数量的折扣价格
     *
     * @param int $quantity
     *
     * @return float|string
     */
    public function calcDiscountPrice(int $quantity)
    {
        /** @var ProductDiscount $discount */
//        $discount = $this->productDiscounts->first(function (ProductDiscount $discount) use ($quantity) {
//            return $discount->quantity === $quantity;
//        });

        // 取最大的折扣数量
        $discount = $this->productDiscounts->where('quantity', '<=', $quantity)
            ->sortBy('quantity')
            ->last();

        if ($discount) {
            return $discount->attributes['price']; // 这里是取原来的价格
        }

        return $this->price;
    }

    public function tryonModels()
    {
        return $this->hasMany(TryonModel::class, 'product_id', 'product_id');
    }

    /**
     * 返回平台对应模型
     *
     * @return mixed
     */
    public function appPlatformModels()
    {
        /** @var TryonModel $query */
        $query = $this->hasMany(TryonModel::class, 'product_id', 'product_id');

        return $query
            ->filterAppPlatform()
            ->orderBy('sort_order');
    }

    /**
     * @param string $keyword
     *
     * @return array|null
     */
    static public function trySearchProductIds(string $keyword)
    {
        try {
            return self::elasticSearchProductIdsByKeyword($keyword);
        } catch (\Exception $exception) {
            return self::getProductIdsByKeyword($keyword);
        }
    }

    /**
     * @param string $keyword
     *
     * @return array
     */
    static public function elasticSearchProductIdsByKeyword(string $keyword): array
    {
        return Product::search($keyword)
            ->get()
            ->pluck('product_id')
            ->all();
    }

    /**
     * @param string $keyword
     *
     * @return array
     */
    static public function getProductIdsByKeyword(string $keyword): array
    {
        return (new self)
            ->columnsKeywordOrLike(['model', 'sku'], $keyword)
            ->pluck('product_id')
            ->all();
    }
}